package service

import (
	"bgs/cache/redisImp"
	"bgs/conf"
	"bgs/db"
	sqlc "bgs/db/sqlc"
	"bgs/grpc/client"
	"bgs/grpc/imp/constants"
	"bgs/grpc/imp/enums"
	"bgs/service/model"
	orderUtil "bgs/service/order"

	"bgs/grpc/gen/proxy2/inventory"
	"bgs/grpc/gen/proxy2/wxpay"
	"bgs/util"
	"context"
	"encoding/json"
	"fmt"
	"strconv"
	"time"

	"go.uber.org/zap"
)

// PaymentServiceImp payment bizlogic
type PaymentServiceImp struct {
	dao        *db.PgDao
	slog       *zap.SugaredLogger
	wechatVars *conf.WechatSection
	vars       *conf.VarsSection
	proxy      *client.GrpcClientProxy
	cacher     *cache.Adapter
}

// NewPaymentService initialization
func NewPaymentService(
	pgdao *db.PgDao,
	slog *zap.SugaredLogger,
	wechatVars *conf.WechatSection,
	vars *conf.VarsSection,
	proxy *client.GrpcClientProxy,
	adp *cache.Adapter,
) *PaymentServiceImp {
	return &PaymentServiceImp{
		dao:        pgdao,
		slog:       slog.With("module", "PaymentService"),
		wechatVars: wechatVars,
		vars:       vars,
		proxy:      proxy,
		cacher:     adp,
	}
}

// PayParam pay order param
type PayParam struct {
	TransMode  enums.EnumTransModel
	OrderPtr   *sqlc.Order
	PaymentPtr *sqlc.Payment
}

// Pay pay order (主动)
func (s *PaymentServiceImp) Pay(ctx context.Context, q *sqlc.Queries, p PayParam) (transactionInfoStr []byte, err error) {
	slog := util.NewContextLogger(ctx, s.slog)

	if p.TransMode == enums.TransModelOrderingPaymentMergeEnforcementIndependant {
		// 创建支付工具的预支付单
		transactionInfo, txErr := s.createPrePayment(ctx, p.OrderPtr)
		if txErr != nil {
			return nil, fmt.Errorf("微信支付代理侧创建预支付单失败:%s", txErr.Error())
		}
		slog.Infof("1 创建微信支付代理侧的预支付单 ok")

		transactionInfoStr, err = json.Marshal(transactionInfo)
		if err != nil {
			return nil, fmt.Errorf("marshal交易信息失败:%s", err.Error())
		}
		_, err := q.UpdatePaymentTransaction(ctx, sqlc.UpdatePaymentTransactionParams{
			ID:              p.PaymentPtr.ID,
			TransactionInfo: sqlc.NRawMessageFromRawMessage(json.RawMessage(transactionInfoStr)),
		})
		if err != nil {
			return nil, err
		}
		slog.Infof("2 更新本地支付单 ok")

		// 更新订单状态
		_, err = q.UpdateOrderStatus(ctx, sqlc.UpdateOrderStatusParams{
			ID:          p.OrderPtr.ID,
			OrderStatus: string(enums.OrderStatusPending),
		})
		if err != nil {
			return nil, err
		}
		slog.Infof("3 更新订单状态=>pending ok")
	} else {
		slog.Infof("1 交易模型决定无需支付,等以后再支付 ok")
	}
	return
}

// RepayParam repay order param
type RepayParam struct {
	OrderSerialNumber string
}

// Repay repay order (主动)
func (s *PaymentServiceImp) Repay(ctx context.Context, q *sqlc.Queries, p RepayParam) (transactionInfoStr []byte, err error) {
	slog := util.NewContextLogger(ctx, s.slog)

	order, err := q.GetOrderBySerialNumber(ctx, p.OrderSerialNumber)
	if err != nil {
		return nil, err
	}
	slog.Infof("1 根据订单号获取订单 ok")
	if order.OrderStatus != string(enums.OrderStatusCreated) && order.OrderStatus != string(enums.OrderStatusPending) {
		return nil, fmt.Errorf("订单状态不允许")
	}

	payment, err := q.GetPaymentByOrderSerialNumber(ctx, order.SerialNumber)
	if err != nil {
		return nil, err
	}
	slog.Infof("1 根据订单号获取本地支付单 ok")
	if payment.PayStatus != string(enums.PayStatusCreated) && payment.PayStatus != string(enums.PayStatusPending) {
		return nil, fmt.Errorf("支付单状态不允许")
	}

	if payment.PayStatus == string(enums.PayStatusCreated) {
		transactionInfo, err := s.createPrePayment(ctx, &order)
		if err != nil {
			return nil, err
		}
		slog.Infof("2 创建微信支付代理侧的预支付单 ok")

		transactionInfoStr, err = json.Marshal(transactionInfo)
		if err != nil {
			return nil, fmt.Errorf("marshal交易信息失败:%s", err.Error())
		}
		payment, err = q.UpdatePaymentTransaction(ctx, sqlc.UpdatePaymentTransactionParams{
			ID:              payment.ID,
			TransactionInfo: sqlc.NRawMessageFromRawMessage(json.RawMessage(transactionInfoStr)),
		})
		if err != nil {
			return nil, err
		}
		slog.Infof("3 更新本地支付单 ok")
	} else {
		transactionInfoStr = payment.TransactionInfo.RawMessage
		slog.Infof("3 获取本地支付单内的交易信息 ok")
	}

	// 更新订单状态
	if order.OrderStatus == string(enums.OrderStatusCreated) {
		// 更新订单状态
		_, err = q.UpdateOrderStatus(ctx, sqlc.UpdateOrderStatusParams{
			ID:          order.ID,
			OrderStatus: string(enums.OrderStatusPending),
		})
		if err != nil {
			return nil, err
		}
		slog.Infof("4 更新订单状态=>pending ok")
	} else {
		slog.Infof("4 订单状态已经是pending ok")
	}
	return
}

// Refund refund amout of a after sale (主动)
func (s *PaymentServiceImp) Refund(ctx context.Context, q *sqlc.Queries, orderPtr *sqlc.Order, afterSalePtr *sqlc.AfterSale) (WxRefundID string, err error) {
	slog := util.NewContextLogger(ctx, s.slog)

	var fundsAccount string
	if orderUtil.CheckIsOrderFinish(orderPtr) && orderUtil.CheckNeedProfitShare(orderPtr) && !orderPtr.IsProfited {
		// 已完成的分账订单未完成全部分账,退款资金账户从可用余额走
		fundsAccount = "AVAILABLE"
	}

	cSyncMappingBrand := s.cacher.FetchSyncMappingByLocalObjAndLocalPk(sqlc.GetSyncMappingByLocalObjAndLocalPkParams{
		LocalObj: constants.SyncLocalObjBrand,
		LocalPk:  strconv.FormatInt(afterSalePtr.BrandID, 10),
	})
	if cSyncMappingBrand == nil {
		err = fmt.Errorf("not found sg_id of brandID:%d", afterSalePtr.BrandID)
		return
	}
	sgID := cSyncMappingBrand.RemotePk

	req := &wxpay.RefundApplyRequest{
		BrandId:      sgID, // 这里的wxpay里的brandId其实是原来的sg_id
		OutTradeNo:   afterSalePtr.OrderSerialNumber,
		OutRefundNo:  afterSalePtr.SerialNumber,
		Amount:       afterSalePtr.Amount,
		FundsAccount: fundsAccount,
	}

	ctxWithTimout, cancel := s.proxy.NewContextWith(ctx)
	defer cancel()

	res, err := s.proxy.WxpayCl.RefundApply(ctxWithTimout, req)
	if err != nil {
		return
	}
	slog.Infof("调用wxpay proxy RefundApply ok")

	WxRefundID = res.RefundId
	return
}

// CloseParam close payment param
type CloseParam struct {
	OrderPtr *sqlc.Order
}

// Close close paymebt (关闭支付单)
func (s *PaymentServiceImp) Close(ctx context.Context, q *sqlc.Queries, p CloseParam) (err error) {
	slog := util.NewContextLogger(ctx, s.slog)

	payment, err := q.ClosePaymentByOrderID(ctx, p.OrderPtr.ID)
	if err != nil {
		return err
	}
	slog.Infof("1 更新本地支付单状态=>closed ok")

	if payment.TransactionNo.Valid {
		// 需要关闭wxpay交易单
		err = s.closePrePayment(ctx, &payment)
		if err != nil {
			return err
		}
		slog.Infof("调用wxpay proxy TradeClose ok")
	} else {
		slog.Infof("无需调用 TradeClose ok")
	}

	return
}

// createPrePayment 创建wxpay的预支付单
func (s *PaymentServiceImp) createPrePayment(ctx context.Context, orderPtr *sqlc.Order) (transactionInfo model.TransactionInfo, err error) {
	// slog := util.NewContextLogger(ctx, s.slog)
	cBrand := s.cacher.FetchBrand(orderPtr.BrandID)
	if cBrand == nil {
		err = fmt.Errorf("not found brand of brandID:%d", orderPtr.BrandID)
		return
	}
	cSyncMappingBrand := s.cacher.FetchSyncMappingByLocalObjAndLocalPk(sqlc.GetSyncMappingByLocalObjAndLocalPkParams{
		LocalObj: constants.SyncLocalObjBrand,
		LocalPk:  strconv.FormatInt(orderPtr.BrandID, 10),
	})
	if cSyncMappingBrand == nil {
		err = fmt.Errorf("not found sg_id of brandID:%d", orderPtr.BrandID)
		return
	}
	sgID := cSyncMappingBrand.RemotePk

	cActivity := s.cacher.FetchActivity(orderPtr.ActivityID)
	if cActivity == nil {
		err = fmt.Errorf("not found activity of activityID:%d", orderPtr.ActivityID)
		return
	}

	// needProfitSharing := orderPtr.NeedProfitToPlatformCommission || orderPtr.NeedProfitToDistributeCommission || orderPtr.NeedProfitToBonus
	needProfitSharing := orderUtil.CheckNeedProfitShare(orderPtr)

	req := &wxpay.JSAPIPreCreateRequest{
		AppId:       s.wechatVars.MinaAppID, //s.vars.AppID,
		MchId:       cBrand.Ext.WxpayMchIDAsService,
		SubMchId:    cBrand.Ext.WxpayMchIDAsSub,
		Description: cActivity.Name,
		OutTradeNo:  orderPtr.SerialNumber,
		TimeExpire:  util.GoTimeToStringf(time.Now().Add(time.Hour*2), util.LayoutSecondWithTimeZoneAndT),
		Amount: &wxpay.AmountInfo{
			Total:    orderPtr.Amount,
			Currency: "CNY",
		},
		Payer: &wxpay.Payer{
			OpenId: orderPtr.UserOpenID,
		},
		SettleInfo: &wxpay.SettleInfo{
			ProfitSharing: needProfitSharing,
		},
		BrandId: sgID, // 这里的wxpay里的brandId其实是原来的sg_id
	}

	ctxWithTimout, cancel := s.proxy.NewContextWith(ctx)
	defer cancel()

	res, err := s.proxy.WxpayCl.JSAPIPreCreate(ctxWithTimout, req)
	if err != nil {
		return
	}

	transactionInfo.AppID = res.AppId
	transactionInfo.TimeStamp = res.TimeStamp
	transactionInfo.Nonce = res.Nonce
	transactionInfo.Package = res.Package
	transactionInfo.SignType = res.SignType
	transactionInfo.PaySign = res.PaySign
	transactionInfo.ExpireTime = res.ExpireTime

	return
}

func (s *PaymentServiceImp) closePrePayment(ctx context.Context, paymentPtr *sqlc.Payment) (err error) {
	cSyncMappingBrand := s.cacher.FetchSyncMappingByLocalObjAndLocalPk(sqlc.GetSyncMappingByLocalObjAndLocalPkParams{
		LocalObj: constants.SyncLocalObjBrand,
		LocalPk:  strconv.FormatInt(paymentPtr.BrandID, 10),
	})
	if cSyncMappingBrand == nil {
		err = fmt.Errorf("not found sg_id of brandID:%d", paymentPtr.BrandID)
		return
	}
	sgID := cSyncMappingBrand.RemotePk

	req := &wxpay.TradeCloseRequest{
		BrandId:    sgID,
		OutTradeNo: paymentPtr.OrderSerialNumber,
	}

	ctxWithTimout, cancel := s.proxy.NewContextWith(ctx)
	defer cancel()

	_, err = s.proxy.WxpayCl.TradeClose(ctxWithTimout, req)
	if err != nil {
		return
	}

	return
}

// AfterPaymentSuccessParam pay success param
type AfterPaymentSuccessParam struct {
	PaymentPtr            *sqlc.Payment
	Payed                 time.Time
	TransactionNo         string
	DistributorCommission uint64
	BonusAmount           uint64
	WxpayCommissionAmount int64
	PlatformCommission    uint64
}

// DoAfterPaymentSuccess payed success do
func (s *PaymentServiceImp) DoAfterPaymentSuccess(ctx context.Context, q *sqlc.Queries, p AfterPaymentSuccessParam) (order sqlc.Order, err error) {
	slog := util.NewContextLogger(ctx, s.slog)

	payment, err := q.UpdatePaymentSuccess(ctx, sqlc.UpdatePaymentSuccessParams{
		ID:            p.PaymentPtr.ID,
		Payed:         sqlc.NTimeFromTime(p.Payed),
		TransactionNo: sqlc.NSFromString(p.TransactionNo),
	})
	if err != nil {
		return
	}
	slog.Infof("2、更新支付单 ok")

	order, err = q.GetOrder(ctx, payment.OrderID)
	if err != nil {
		return
	}
	slog.Infof("3、获取订单 ok")

	orderDetails, err := q.ListNormalOrderDetailsByOrderID(ctx, order.ID)
	if err != nil {
		return
	}
	slog.Infof("4、获取订单明细 ok")

	order, err = q.FlowOrderFromPendingToPayed(ctx, sqlc.FlowOrderFromPendingToPayedParams{
		ID:                       payment.OrderID,
		ActualAmount:             payment.Amount,
		DistributorCommission:    int64(0 - p.DistributorCommission), // DistributorCommission 对于商家来说是支出所以用负数
		BonusAmount:              int64(0 - p.BonusAmount),           // BonusAmount 对于商家来说是支出所以用负数
		WxpayCommissionAmount:    0 - p.WxpayCommissionAmount,
		PlatformCommissionAmount: int64(0 - p.PlatformCommission),
	})
	if err != nil {
		return
	}
	slog.Infof("5、订单流转到已支付 ok")

	deliveryNumber, warehouseID, err := s.createCombinedProcessOrder(ctx, order)
	if err != nil {
		return
	}
	slog.Infof("6、创建仓库侧履约单 ok")

	enforcement, err := q.UpsertEnforcement(ctx, sqlc.UpsertEnforcementParams{
		OrderID:           order.ID,
		OrderSerialNumber: order.SerialNumber,
		Delivered:         sqlc.NTimeFromTime(time.Now()),
		OrderDeliveryKey:  util.AddSuffixWithParentheses(order.SerialNumber, deliveryNumber),
		DeliveryNumber:    sqlc.NSFromString(deliveryNumber),
	})
	slog.Infof("7 插入/更新本地履约单 ok")

	// 目前一个订单对应一个履约单
	for _, item := range orderDetails {
		_, err = q.UpsertEnforcementDetail(ctx, sqlc.UpsertEnforcementDetailParams{
			EnforcementID:     enforcement.ID,
			OrderID:           item.OrderID,
			OrderSerialNumber: item.OrderSerialNumber,
			ProductID:         item.ProductID,
			ProductSpecID:     item.ProductSpecID,
			ProductSpecKey:    item.ProductSpecKey,
			WarehouseID:       warehouseID,
			Quantity:          item.Quantity,
		})
		if err != nil {
			return
		}
	}
	slog.Infof("8 插入/更新本地履约单明细 ok")

	return
}

// CreateCombinedProcessOrder 创建仓库侧履约单
func (s *PaymentServiceImp) createCombinedProcessOrder(ctx context.Context, order sqlc.Order) (deliveryNumber string, warehouseID int64, err error) {
	slog := util.NewContextLogger(ctx, s.slog)
	deliveryMode := sqlc.EmDeliveryMode(order.DeliveryMode)

	orderExtInfo := &model.OrderExtInfo{}
	err = json.Unmarshal(order.ExtInfo, orderExtInfo)
	if err != nil {
		err = fmt.Errorf("unmarshal ext_info失败:%s", err.Error())
		return
	}

	warehouseID = orderExtInfo.WarehouseID
	cWarehouse := s.cacher.FetchWarehouse(warehouseID)
	if cWarehouse == nil {
		err = fmt.Errorf("not found warehouse :%d", warehouseID)
		return
	}
	ckID := cWarehouse.CkID
	fLng, err := strconv.ParseFloat(cWarehouse.Lng, 64)
	if err != nil {
		return
	}
	fLat, err := strconv.ParseFloat(cWarehouse.Lat, 64)
	if err != nil {
		return
	}

	var details []*inventory.CreateProcessOrderDetail
	for _, item := range orderExtInfo.Details {
		details = append(details, &inventory.CreateProcessOrderDetail{
			StorageKey: item.StorageKey,
			Quantity:   int64(item.Quantity),
			Price:      int64(item.Price),
			Origin:     int64(item.Price * item.Quantity),
			Remark: &inventory.ProcessDetailRemark{
				Name: item.ProductWithSpecName,
				Unit: "份",
			},
		})
	}

	//  // 商品应付/应退(分摊)金额，不含配送费（单位:分）
	//  int64 prod_trans = 3;
	//  // 配送费用(自提&无需配送&退货为0)（单位:分）
	//  int64 delivery_fee = 4;
	//  // 配送模式
	//  DeliverType delivery_type = 5;

	deliveryType := util.IfThenElse(order.DeliveryMode == string(sqlc.EmDeliveryModeSelfPickup), inventory.DeliverType_PICK_UP, inventory.DeliverType_ORDINARY).(inventory.DeliverType)

	processOrderInfo := &inventory.CreateProcessOrderInfo{
		ExtOrderNumber: order.SerialNumber,
		WarehouseId:    ckID, // 此处应是远端的仓库id
		ProdTrans:      order.ProductAmount,
		DeliveryFee:    int64(order.ShippingFee),
		DeliveryType:   deliveryType,
		Details:        details,
	}
	if deliveryMode != sqlc.EmDeliveryModeSelfPickup {
		processOrderInfo.DeliveryFee = int64(order.ShippingFee)
		processOrderInfo.DeliveryType = util.IfThenElse(deliveryMode == sqlc.EmDeliveryModeExpress, inventory.DeliverType_ORDINARY, inventory.DeliverType_CITY).(inventory.DeliverType) // 配送模式0~自提，1~同城，2~普快
	} else {
		processOrderInfo.DeliveryFee = 0
		processOrderInfo.DeliveryType = inventory.DeliverType_PICK_UP
	}

	req := &inventory.CreateCombinedProcessOrderRequest{
		FreezeOrderNumber: order.InventoryFreezeOrderNo.String,
		MemberId:          strconv.FormatInt(order.UserID, 10),

		OrderList: []*inventory.CreateProcessOrderInfo{
			processOrderInfo,
		},
	}

	if deliveryMode != sqlc.EmDeliveryModeSelfPickup {
		req.AddressInfo = &inventory.AddressInfo{
			Lng:      fLng,
			Lat:      fLat,
			CityCode: cWarehouse.CityCode,
			Contact:  cWarehouse.Linkman,
			Mobile:   cWarehouse.Tel,
			Address:  cWarehouse.Address,
		}
	}

	ctxWithTimout, cancel := s.proxy.NewContextWith(ctx)
	defer cancel()

	reply, err := s.proxy.InventoryCl.CreateCombinedProcessOrder(ctxWithTimout, req)
	if err != nil {
		errReply := s.proxy.GetInventoryErrorInfo(err)
		if errReply != nil {
			err = fmt.Errorf("仓库侧创建履约单错误:%s", errReply.ErrorMessage)
			slog.Infof("InventoryCl.CreateCombinedProcessOrder err====>type:%s,code:%s,msg:%s", errReply.ErrorType, errReply.ErrorCode, errReply.ErrorMessage)
		}
		return
	}

	results := reply.Result
	if lr := len(results); lr != 1 {
		err = fmt.Errorf("仓库侧创建履约单返回不匹配:%d", lr)
		return
	}
	processOrder := results[0]
	deliveryNumber = processOrder.OrderNumber
	slog.Infof("1 创建仓库侧履约单 ok")
	return
}

// AfterRefundSuccessParam refund success param
type AfterRefundSuccessParam struct {
	AfterSaleID                 int64
	Refunded                    time.Time
	RefundDistributorCommission uint64
	RefundBonusAmount           uint64
	RefundWxpayCommissionAmount uint64
	RefundPlatformCommission    uint64
}

// DoAfterRefundSuccess refunded success do
func (s *PaymentServiceImp) DoAfterRefundSuccess(ctx context.Context, q *sqlc.Queries, p AfterRefundSuccessParam) (afterSale sqlc.AfterSale, err error) {
	slog := util.NewContextLogger(ctx, s.slog)

	afterSale, err = q.FinishAfterSale(ctx, sqlc.FinishAfterSaleParams{
		ID:                       p.AfterSaleID,
		Refunded:                 sqlc.NTimeFromTime(p.Refunded),
		DistributorCommission:    int64(p.RefundDistributorCommission),
		BonusAmount:              int64(p.RefundBonusAmount),
		WxpayCommissionAmount:    int64(p.RefundWxpayCommissionAmount),
		PlatformCommissionAmount: int64(p.RefundPlatformCommission),
	})
	slog.Infof("2、完成售后单 ok")

	return
}
